package tpase.GlobalServer.Controller;

import it.unisa.dia.gas.jpbc.Element;
import it.unisa.dia.gas.jpbc.Pairing;
import tpase.GlobalServer.DO.*;
import tpase.GlobalServer.Handler.SetupNettyServerHandler;
import tpase.GlobalServer.Service.Impl.GlobalServerServiceImpl;
import tpase.GlobalServer.Service.GlobalServerService;
import tpase.util.Util;

import java.math.BigInteger;
import java.util.*;

public class GlobalServerController {
    private final GlobalServerService globalServerService = new GlobalServerServiceImpl();

    // 参数路径
    private final static String paramPath = "config/parameters.properties";
    private final static String jpbcPath = "config/a.properties";

    private int t, n;
    private String ip;
    private int port;
    // 秘密共享主密钥
    private Element s;
    // 秘密共享份额
    private List<Share> shares;
    private PublicParam publicParam;
    // gamma_i = g_1^{s_i}
    private List<byte[]> gammaList;
    private List<byte[]> gamma_tsiList;
    private Element gamma_s;
    private List<PublicCommitment> publicCommitmentList = new ArrayList<>();
    private List<GroupGenerator> groupGeneratorList = new ArrayList<>();
    private PublicParameter publicParameter;
    private List<Address> serverList = new ArrayList<>();

    public GlobalServerController(boolean isSetup) {
        loadParameters();
        // 全服服务器未初始化
        if (!isSetup) {
            s = getRand();
            shares = SSS(n, t, s);
            setupG();
            setGamma();
            publicParameter = new PublicParameter(t, n);
            // 写入数据库
            // storeGroupGenerator
            // storePublicCommitment
            globalServerService.storeGroupGenerator(groupGeneratorList);
            globalServerService.storePublicCommitment(publicCommitmentList);
            globalServerService.storePublicParameter(publicParameter);
        } else {
            // 全局服务器已经初始化
            // 从数据库读取参数
            groupGeneratorList = globalServerService.getGroupGenerator();
            publicCommitmentList = globalServerService.getPublicCommitment();
            publicParameter = globalServerService.getPublicParameter();
        }
    }

    /**
     * GlobalServer 启动
     */
    public void start() {
        Set<Address> serverSet = new HashSet<>();
        new Thread(() -> {
            Util.processServerBootstrap(new SetupNettyServerHandler(publicParameter, groupGeneratorList, publicCommitmentList, shares, serverSet, serverList), port);
        }).start();
    }

    private void setupG() {
        publicParam = new PublicParam(jpbcPath);
        for (Map.Entry<String, byte[]> entry : publicParam.getMap().entrySet()) {
            groupGeneratorList.add(new GroupGenerator(entry.getKey(), entry.getValue()));
        }
    }

    private void setGamma() {
        gammaList = new ArrayList<>();
        gamma_tsiList = new ArrayList<>();
        Map<String, byte[]> map = publicParam.getMap();
        Element g1 = getElementFromBytes(map.get("g1"), TypeG.G1);
        Element gt = getElementFromBytes(map.get("gT"), TypeG.GT);
        for (int i = 0; i < shares.size(); i++) {
            Element gamma_si = g1.duplicate().powZn(shares.get(i).getY());
            Element gamma_tsi = gt.duplicate().powZn(shares.get(i).getY());
            gammaList.add(gamma_si.toBytes());
            gamma_tsiList.add(gamma_tsi.toBytes());
            map.put("gamma_s" + shares.get(i).getX().toString(), gamma_si.toBytes());
            map.put("gamma_ts" + shares.get(i).getX().toString(), gamma_tsi.toBytes());
            publicCommitmentList.add(new PublicCommitment(shares.get(i).getX().toString(), gamma_si.toBytes(), gamma_tsi.toBytes()));
        }
        gamma_s = g1.duplicate().powZn(s.duplicate());
        publicCommitmentList.add(new PublicCommitment("-1", gamma_s.toBytes(), new byte[1]));
        map.put("gamma_s", gamma_s.toBytes());
    }

    private Element getElementFromBytes(byte[] bytes, TypeG typeG) {
        Pairing pairing = Util.getPairing(jpbcPath);
        Element e = null;
        switch (typeG) {
            case G1:
                e = pairing.getG1().newElementFromBytes(bytes);
                break;
            case G2:
                e = pairing.getG2().newElementFromBytes(bytes);
                break;
            case GT:
                e = pairing.getGT().newElementFromBytes(bytes);
                break;
            case Zr:
                e = pairing.getZr().newElementFromBytes(bytes);
                break;
            default:
                System.out.println("Type_G is error");
        }
        return e;
    }

    private void loadParameters() {
        t = Integer.parseInt(Util.getProperty(paramPath, "t"));
        n = Integer.parseInt(Util.getProperty(paramPath, "n"));
        ip = Util.getProperty(paramPath, "ip");
        port = Integer.parseInt(Util.getProperty(paramPath, "port"));
    }

    private List<Share> SSS(int n, int t, Element s) {
        if (t > n) {
            System.out.println("t cannot be greater than n");
            return null;
        } else if (t < 2) {
            System.out.println("t must be greater than 1");
            return null;
        }

        List<Element> coefficients = getCoefficients(t);
        List<Share> shares = getShares(t, n, coefficients, s);

        return shares;
    }

    /**
     * 获取t个随机值作为多项式的系数
     * @param t 门限值
     * @return t个系数的List
     */
    private List<Element> getCoefficients(int t) {
        ArrayList<Element> res = new ArrayList<>();
        for (int i = 0; i < t - 1; i++) {
            res.add(getRand());
        }
        return res;
    }

    private List<Share> getShares(int t, int n, List<Element> coefficients, Element s) {
        ArrayList<Share> shares = new ArrayList<>();
        List<Element> xList = getXList(n);

        for (int i = 0; i < n; i++) {
            Element y = s.duplicate();
            Element x = xList.get(i);
            // polynomial coefficients f(x) = coeff[1] x^{1} + coeff[2] x^{2} + ... + coeff[t - 1] x^{t - 1} + s
            for (int j = 0; j < coefficients.size(); j++) {
                Element pow_j = Util.getPairing(jpbcPath).getZr().newElement(j + 1);
                // coeff[j] * x^{j + 1}
                y.add(coefficients.get(j).duplicate().mulZn(x.duplicate().powZn(pow_j)));
            }
            shares.add(new Share(x, y));
        }
        return shares;
    }


    /**
     * 获取n个随机值作为x
     * @param n 总密钥服务器数量
     * @return n个x的List
     */
    private List<Element> getXList(int n) {
        ArrayList<Element> xList = new ArrayList<>();
        // avoid repeated x
        Set<BigInteger> set = new HashSet<>();
        for (int i = 0; i < n; i++) {
            Element e;
            do {
                e = getRand();
            } while (set.contains(new BigInteger(e.toBytes())));
            xList.add(e);
            set.add(new BigInteger(e.toBytes()));
        }
        return xList;
    }

    private Element getRand() {
        Pairing pairing = Util.getPairing(jpbcPath);
        return pairing.getZr().newRandomElement().getImmutable();
    }

}
